# -*- coding: utf-8 -*-
# @Time    : 2024/6/5 18:23
# @Author  : yujiahao
# @File    : 05_encoding_base.py
# @description: 了解几种编码和进制以及代码风格和编程惯例

# todo 1、编码
"""
一.编码
咱们的电脑，存储和发送文件，发送的是什么？电脑里面是不是有成千上万个二极管，亮的代表是1，不亮的代表是0，这样实际上电脑的存储和发送是不是都是010101啊

我们发送的内容都是010101010这样写的内容比较多就不知道是什么了,所以我们想要明确的区分出来发送的内容就需要在某个地方进行分段.计算机中设定的就是8位一断句

1.1 ASCII
    计算机:

        储存文件,或者是传输文件,实际上是010101010

        计算机创建初期,美国,是7位一段，但是发明者说为了拓展，留出一位，这样就是8位一段句。8位有多少种可能 ？256

        密码本:

        ascii

             00000001

             01000001 01000010 01000011   ABC

    随着计算机的发展. 以及普及率的提高. 流行到欧洲和亚洲. 这时ASCII码就不合适了. 比如: 中文汉字有几万个. 而ASCII多也就256个位置. 所以ASCII不行了. 怎么办呢? 这时, 不同的国家就提出了不同的编码用来适用于各自的语言环境. 比如, 中国的GBK, GB2312, BIG5, ISO-8859-1等等. 这时各个国家都可以使用计算机了.

1.2 GBK
    GBK, 国标码占用2个字节. 对应ASCII码 GBK直接兼容. 因为计算机底层是用英文写的. 你不支持英文肯定不行. 而英文已经使用了ASCII码. 所以GBK要兼容ASCII. 这里GBK国标码. 前面的ASCII码部分. 由于使用两个字节. 所以对于ASCII码而言. 前9位都是0

    字母A:0100 0001 # ASCII
    字母A:0000 0000 0100 0001 # 国标码

    随着全球化的普及，发展到欧洲，亚洲等国家，发现这些根本不够用，所以创建了万国码。 因为全球语言很多,ascii不足以存储这么多对应关系,创建了一个超级密码本:万国码unicode

        8 位 == 1个字节.
        hello h一个字符,e一个字符,he就不是一个字符.
        中国:中是一个字符,国是一个字符.

1.3 Unicode
    创建之初,16位,2个字节,表示一个字符. 英文: a b c 六个字节 一个英文2个字节 中文 中国 四个字节 一个中文用2个字节

    但是这种也不行，这种最多有65535种可能，可是中国文字有9万多，所以改成 32位,4个字节,表示一个字符.

    a 01000001 01000010 01000011 00000001 b 01000001 01000010 01100011 00000001 中 01001001 01000010 01100011 00000001 浪费资源.

1.4 UTF-8
    对Unicode进行升级: utf-8 utf-8 用最少用8位数,去表示一个字符. 英文: 8位,1个字节表示. 欧洲文字: 16位,两个字节表示一个字符. 中文,亚洲文字: 24位,三个字节表示.

    utf-16 用最少用16位数。

    gbk: 国标,只能中国人自己用, 一个中文用16位,两个字节表示。

1.5 单位转化
    8bit = 1byte
    1024byte = 1KB
    1024KB = 1MB
    1024MB = 1GB
    1024GB = 1TB
    1024TB = 1PB
    1024PB = 1EB
    1024EB = 1ZB
    1024ZB = 1YB
    1024YB = 1NB
    1024NB = 1DB
    常用到TB就够了
"""

# todo 2、 二次编码

'''
简单来看:

    ASCII : 最早的编码. ⾥⾯有英⽂⼤写字⺟, ⼩写字⺟, 数字, ⼀些特殊字符. 没有中⽂, 8个01代码, 8个bit, 1个byte

    GBK: 中⽂国标码, ⾥⾯包含了ASCII编码和中⽂常⽤编码. 16个bit, 2个byte

    UNICODE: 万国码, ⾥⾯包含了全世界所有国家⽂字的编码. 32个bit, 4个byte, 包含了 ASCII

    UTF-8: 可变⻓度的万国码. 是unicode的⼀种实现. 最⼩字符占8位 　　

        1.英⽂: 8bit 1byte 　　2.欧洲⽂字:16bit 2byte 　　3.中⽂:24bit 3byte

综上, 除了ASCII码以外, 其他信息不能直接转换. 在python3的内存中. 在程序运⾏阶段. 使⽤的是unicode编码. 因为unicode是万国码. 什么内容都可以进⾏显⽰. 那么在数据传输和存储的时候由于unicode比较浪费空间和资源. 需要把 unicode转存成UTF-8或者GBK进⾏存储. 怎么转换呢. 在python中可以把⽂字信息进⾏编码. 编码之后的内容就可以进⾏传输了. 编码之后的数据是bytes类型的数据.其实啊. 还是原来的 数据只是经过编码之后表现形式发⽣了改变⽽已.

bytes的表现形式:

    英⽂ b'alex' 英⽂的表现形式和字符串没什么两样
    中⽂ b'\xe4\xb8\xad' 这是⼀个汉字的UTF-8的bytes表现形式


'''

# 1.1 编码
s = "alex"
print(f"将字符串编码成UTF-8: {s.encode('utf-8')}")
print(f"将字符串编码成GBK: {s.encode('GBK')}")

s = "中"
print(f"中文编码成UTF-8: {s.encode('UTF-8')}")
print(f"中文编码成GBK: {s.encode('GBK')}")

'''
记住: 英⽂编码之后的结果和源字符串⼀致. 中⽂编码之后的结果根据编码的不同. 编码结果也不同. 　　 
我们能看到. ⼀个中⽂的UTF-8编码是3个字节. ⼀个GBK的中⽂编码是2个字节. 　　 
编码之后的类型就是bytes类型. 在⽹络传输和存储的时候我们python是保存和存储的bytes 类型. 　　 
那么在对⽅接收的时候. 也是接收的bytes类型的数据. 我们可以使⽤decode()来进⾏解码操作. 　　 
把bytes类型的数据还原回我们熟悉的字符串:
'''

s = "我叫李嘉诚"
print(f"将字符串编码成UTF-8: {s.encode('utf-8')}")

encoded_bytes = b'\xe6\x88\x91\xe5\x8f\xab\xe6\x9d\x8e\xe5\x98\x89\xe8\xaf\x9a'
print(f"解码这个字节串: {encoded_bytes.decode('utf-8')}")

# 1.2 解码  编码和解码的时候都需要制定编码格式.

'''
这里为了方便对比Python代码的简洁性，我这里提供一段Java代码用来把一个GBK的文本文件内容读取后存储到一个UTF-8的文本文件中：

实现步骤是：
     
    1、初始化文件流：
    
        - 使用FileInputStream打开一个名为"旧文件名_gbk"的文件，该文件假定为GBK编码。
        - 创建一个InputStreamReader，将FileInputStream与GBK编码关联，用于正确解读文件中的字节。
    
    2、资源清理：
    
        - 在finally块中，确保所有打开的流（FileInputStream, InputStreamReader, FileOutputStream, OutputStreamWriter）都被关闭，无论过程中是否发生异常。
    
    3、设置输出文件流：
    
        - 使用FileOutputStream创建或覆盖一个名为"新文件名_utf8"的文件。
        - 创建一个OutputStreamWriter，将FileOutputStream与UTF-8编码关联，用于将字符以UTF-8编码写入文件。
    4、读取和写入操作：
    
        - 通过InputStreamReader读取字符到一个字符数组中，每次最多读取100个字符。
        - 使用OutputStreamWriter将读取的字符数组写入输出文件，直到文件末尾。
    5、异常处理：
    
        - 捕获并处理可能发生的异常，例如文件不存在或编码不支持等。



@Test
    public void test17() {


        FileInputStream fileInputStream = null;

        InputStreamReader inputStreamReader = null;

        FileOutputStream fileOutputStream = null;

        OutputStreamWriter outputStreamWriter = null;

        try {
            fileInputStream = new FileInputStream("旧文件名_gbk");
            inputStreamReader = new InputStreamReader(fileInputStream, "gbk");

            fileOutputStream = new FileOutputStream("新文件名_utf8");
            outputStreamWriter = new OutputStreamWriter(fileOutputStream, "utf8");

            char[] chars = new char[100];
            int count;
            while ((count = inputStreamReader.read(chars))!=-1){

                outputStreamWriter.write(chars,0,count);
            }


        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputStreamReader != null) {
                try {
                    inputStreamReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
           if (fileOutputStream != null) {
                    try {
                        fileOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
           if (outputStreamWriter != null) {
                    try {
                        outputStreamWriter.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }
        }

    }

如果用Python的话就很简单：

    
    def convert_file_encoding():
        try:
            # 使用 'with' 语句自动管理资源，确保文件无论如何都能正确关闭
            with open("旧文件名_gbk", "r", encoding="gbk") as file_input, \
                 open("新文件名_utf8", "w", encoding="utf-8") as file_output:
    
                # 读取和写入操作
                while True:
                    chars = file_input.read(100)  # 读取最多100个字符
                    if not chars:
                        break  # 如果没有字符读取，结束循环
                    file_output.write(chars)  # 写入读取的字符
    
        except Exception as e:
            # 打印异常信息
            print(f"An error occurred: {e}")
    
    # 调用函数
    convert_file_encoding()

'''
s = "我是文字"
bs = s.encode("GBK")
print(f"将字符串编码成GBK: {bs}")

s = bs.decode("GBK")  # 解码
print(f"将GBK编码的字节串解码成Unicode字符串: {s}")

bss = s.encode("UTF-8")  # 重新编码
print(f"将Unicode字符串重新编码成UTF-8: {bss}")

# todo 3、几种进制

"""

先看一句话：
世界上有10种人 ，认识和不认识二进制的。进制只是表达形式不一样，本质上还是一回事情，这里主要以十进制开头研究二进制和十六进制。

对于整数，有四种表示方式：
    二进制：0,1 ，满2进1.以0b或0B开头。
    十进制：0-9 ，满10进1.
    八进制：0-7 ，满8进1. 以数字0开头表示。
    十六进制：0-9及A-F，满16进1. 以0x或0X开头表示。此处的A-F不区分大小写。
            如：0x21AF +1= 0X21B0

举例体会一下进制：计算 713625

1）十进制

    7 * 10^5 + 
    
    1 * 10^4  +
    
    3 * 10^3  +
    
    6 * 10^2  +
    
    2 * 10^1  +
    
    5 * 10^0
    
    权值：以10为底的 n次幂，没有10这个数字，所以逢10进1；
    
    总结：二进制、八进制、十六进制 、甚至M进制，权值都是以M为底n次幂，没有M这个数字，所以逢M进1；

2）十进制、十六进制、二进制对比	

    | 十进制 | 十六进制(要加0x) | 二进制 |
    | :----: | :--------------: | :----: |
    |   0    |        0         |  0000  |
    |   1    |        1         |  0001  |
    |   2    |        2         |  0010  |
    |   3    |        3         |  0011  |
    |   4    |        4         |  0100  |
    |   5    |        5         |  0101  |
    |   6    |        6         |  0110  |
    |   7    |        7         |  0111  |
    |   8    |        8         |  1000  |
    |   9    |        9         |  1001  |
    |   10   |        A         |  1010  |
    |   11   |        B         |  1011  |
    |   12   |        C         |  1100  |
    |   13   |        D         |  1101  |
    |   14   |        E         |  1110  |
    |   15   |        F         |  1111  |

每个16进制数正好可以唯一的对应4个2进制数, 2个16进制数正好机械对应8个2进制数，因此得出转换规律 8421

3）十六进制、二进制转换

 无敌的8421记住这个就行：

练习 : //    8  4   2  1
    1、十六进制 二进制互转
        将十六进制：0xA9384D5F
    
        转换为二进制：1010 1001 0011 1000 0100 1011 0101 1111 
    
        将二进制：0101 0101 0111 1110 1000 0110 0101 1101
    
        转换为十六进制：0x557E865D
        
	
4）十进制转二进制
    例1：十进制10转换成二进制
        首先按照8421来分解：
        10=8+2
        用8421转换法，对应数字下面写1，没有的写0，得到：
        
            8	4	2	1
            1	0	1	0
        也就是说十进制的10转换成二进制等于1010，
        此时或许有人会问，如果这个数大于15（8+4+2+1）咋办？
        很简单， 如果大于15，就在前面添加个16；
        如果再大于31，就再在前面加个32

    例2：十进制21转换成二进制
        首先按照8421来分解：
        21=16+4+1
        用8421转换法，对应数字下面写1，没有的写0，得到：
        
        16	8	4	2	1
        1	0	1	0	1
    例3：十进制35转换成二进制
        首先按照8421来分解：
        35=32+2+1
        用8421转换法，对应数字下面写1，没有的写0，得到：
        
        32	16	8	4	2	1
        1	0	0	0	1	1
5）二进制转十进制

例1：二进制1011转换成十进制
    8	4	2	1
    1	0	1	1
    8+2+1=11(D)
    二进制1011就是十进制的11
    (其实是上面方法的逆过程!）


	

6）十六进制、十进制转换

    将十六进制：0x15E、0x3D
    
    转为十进制过程：1 * 16^2 +5 *16^1  +14 * 16^0   、 3 *16^1+13 * 16^0 
    
    转为十进制结果：350、61

7）原码、补码、反码

在计算机底层中都是以二进制的补码形式存在的，正数的原码、反码和补码一样在此不多研究，主要是研究负数的原码、反码和补码之间的转换。

//计算机中二进制左边最高位为符号位，1表示负数，0表示正数。

补码：正数的三码合一，不多做研究，直接进行运算，下面说的是负数

负数的原码等于补码减一然后在取反//相反负数的补码是原码取反后加一得到

    练习1：负数的补码转原码以及转换成十进制
    
        补码（注意最高位是负数）：             1	0	1	0		 0	0	0	1
    
        第一步减一：						   1	0	1	0 		0	0	0	0
    
        第二步取反得到原码:		           0	1	0	1 		1	1	1	1

        转换十六进制//8421：0x5F
            计算过程：十六进制是以 0x开头，利用二进制和8421套值（没数个只要是1就按照顺序记为几然后相加）
             二进制：0	1	0	1 		1	1	1	1
             套值：  8  4   2   1       8   4   2   1
             相加: 4+1 = 5 作为十六进制第一个数， 8+4+2+1 = 15作为第二个数
             十六进制：0x5F    
    
        转换为十进制:-95 //注意别忘了加负号

     练习2：正数的补码以及转换成十进制

        补码（注意最高位是正数）：	0	1	1	0		 1	1	0	1
        
        原码（和补码完全相同）：	0	1	1	0		 1	1	0	1
        
        转换十六进制//8421：0x6D
        
        转换为十进制:109

8)几个数据类型的二进制极值表示

①1111 1111是负数        -1 => 1111 1110    取反 => 0000 0001 => 1  所以1111 1111 是 -1;

②byte型（1个字节--对应8个2进制数 ）

    最大值：0111 1111 => 0x7F => 7*16+15 => 127;

    最小值：1000 0000 是负数   -1 => 0111 1111  取反 => 1000 0000 => 0x80 => 16*8 => 128  ;   

 	所以1000 0000 是：-128;

④short型（2个字节--对应16个2进制数）

	最大值：0111 1111 1111 1111 => 0x7FFF => 32767;

	最小值：1000 0000 0000 0000 => 0x8000 => -32768;

⑤int型（四个字节--对应32个2进制数）

	最大值：0111 1111 1111 1111 1111 1111 1111 1111 => 0x7FFFFFFF;

	最小值:1000 0000 0000 0000 0000 0000 0000 0000 => 0x80000000;

⑥long型（8个字节--对应64个2进制数）

	最大值：0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  			=>  0x7FFFFFFFFFFFFFFF;

	最小值:1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

			=> 0x8000000000000000;
"""

# todo 4、代码风格

'''
PEP 8风格指南

PEP是Python Enhancement Proposal的缩写，通常翻译为“Python增强提案”。
每个PEP都是一份为Python社区提供的指导Python往更好的方向发展的技术文档，
其中的第8号增强提案（PEP 8）是针对Python语言编订的代码风格指南。
尽管我们可以在保证语法没有问题的前提下随意书写Python代码，但是在实际开发中，
采用一致的风格书写出可读性强的代码是每个专业的程序员应该做到的事情，
也是每个公司的编程规范中会提出的要求，这些在多人协作开发一个项目（团队开发）的时候显得尤为重要。
我们可以从Python官方网站的[PEP 8链接](https://www.python.org/dev/peps/pep-0008/)中找到该文档，
下面我们对该文档的关键部分做一个简单的总结。

空格的使用
    
    1. <u>使用空格来表示缩进而不要用制表符（Tab）。</u>这一点对习惯了其他编程语言的人来说简直觉得不可理喻，
    因为绝大多数的程序员都会用Tab来表示缩进，但是要知道Python并没有像C/C++或Java那样的用花括号来构造一个代码块的语法，
    在Python中分支和循环结构都使用缩进来表示哪些代码属于同一个级别，鉴于此Python代码对缩进以及缩进宽度的依赖比其他很多语言都强得多。
    在不同的编辑器中，Tab的宽度可能是2、4或8个字符，甚至是其他更离谱的值，用Tab来表示缩进对Python代码来说可能是一场灾难。
    2. <u>和语法相关的每一层缩进都用4个空格来表示。</u>
    3. <u>每行的字符数不要超过79个字符，如果表达式因太长而占据了多行，除了首行之外的其余各行都应该在正常的缩进宽度上再加上4个空格。</u>
    4. <u>函数和类的定义，代码前后都要用两个空行进行分隔。</u>
    5. <u>在同一个类中，各个方法之间应该用一个空行进行分隔。</u>
    6. <u>二元运算符的左右两侧应该保留一个空格，而且只要一个空格就好。</u>

标识符命名

PEP 8倡导用不同的命名风格来命名Python中不同的标识符，以便在阅读代码时能够通过标识符的名称来确定该标识符在Python中扮演了怎样的角色
（在这一点上，Python自己的内置模块以及某些第三方模块都做得并不是很好）。

    1. <u>变量、函数和属性应该使用小写字母来拼写，如果有多个单词就使用下划线进行连接。</u>
    2. <u>类中受保护的实例属性，应该以一个下划线开头。</u>
    3. <u>类中私有的实例属性，应该以两个下划线开头。</u>
    4. <u>类和异常的命名，应该每个单词首字母大写。</u>
    5. <u>模块级别的常量，应该采用全大写字母，如果有多个单词就用下划线进行连接。</u>
    6. <u>类的实例方法，应该把第一个参数命名为`self`以表示对象自身。</u>
    7. <u>类的类方法，应该把第一个参数命名为`cls`以表示该类自身。</u>

表达式和语句

在Python之禅（可以使用`import this`查看）中有这么一句名言：
    “There should be one-- and preferably only one --obvious way to do it.”，
    翻译成中文是“做一件事应该有而且最好只有一种确切的做法”，这句话传达的思想在PEP 8中也是无处不在的。

    1. <u>采用内联形式的否定词，而不要把否定词放在整个表达式的前面。</u>例如`if a is not b`就比`if not a is b`更容易让人理解。
    2. 不要用检查长度的方式来判断字符串、列表等是否为`None`或者没有元素，应该用`if not x`这样的写法来检查它。
    3. <u>就算`if`分支、`for`循环、`except`异常捕获等中只有一行代码，也不要将代码和`if`、`for`、`except`等写在一起，分开写才会让代码更清晰。</u>
    4. <u>`import`语句总是放在文件开头的地方。</u>
    5. <u>引入模块的时候，`from math import sqrt`比`import math`更好。</u>
    6. <u>如果有多个`import`语句，应该将其分为三部分，从上到下分别是Python**标准模块**、三方模块和自定义模块，每个部分内部应该按照模块名称的字母表顺序来排列。</u>

'''


# todo 5、Python编程惯例

"""
Python编程惯例

“惯例”这个词指的是“习惯的做法，常规的办法，一贯的做法”，与这个词对应的英文单词叫“idiom”。由于Python跟其他很多编程语言在语法和使用上还是有比较显著的差别，因此作为一个Python开发者如果不能掌握这些惯例，就无法写出“Pythonic”的代码。下面我们总结了一些在Python开发中的惯用的代码。

1. 让代码既可以被导入又可以被执行。

   if __name__ == '__main__':

2. 用下面的方式判断逻辑“真”或“假”。

   if x:
   if not x:

   好的代码：

   name = 'jackfrued'
   fruits = ['apple', 'orange', 'grape']
   owners = {'1001': '骆昊', '1002': '王大锤'}
   if name and fruits and owners:
       print('I love fruits!')

   不好的代码：

   name = 'jackfrued'
   fruits = ['apple', 'orange', 'grape']
   owners = {'1001': '骆昊', '1002': '王大锤'}
   if name != '' and len(fruits) > 0 and owners != {}:
       print('I love fruits!')

3. 善于使用in运算符。

   if x in items: # 包含
   for x in items: # 迭代

   好的代码：

   name = 'Hao LUO'
   if 'L' in name:
       print('The name has an L in it.')

   不好的代码：

   name = 'Hao LUO'
   if name.find('L') != -1:
       print('This name has an L in it!')

4. 不使用临时变量交换两个值。

   a, b = b, a

5. 用序列构建字符串。

   好的代码：

   chars = ['j', 'a', 'c', 'k', 'f', 'r', 'u', 'e', 'd']
   name = ''.join(chars)
   print(name)  # jackfrued

   不好的代码：

   chars = ['j', 'a', 'c', 'k', 'f', 'r', 'u', 'e', 'd']
   name = ''
   for char in chars:
       name += char
   print(name)  # jackfrued

6. EAFP优于LBYL。

   EAFP - Easier to Ask Forgiveness than Permission.
   LBYL - Look Before You Leap.

   好的代码：

   d = {'x': '5'}
   try:
       value = int(d['x'])
       print(value)
   except (KeyError, TypeError, ValueError):
       value = None

   不好的代码：

   d = {'x': '5'}
   if 'x' in d and isinstance(d['x'], str) \
       and d['x'].isdigit():
       value = int(d['x'])
       print(value)
   else:
       value = None

7. 使用enumerate进行迭代。

   好的代码：

   fruits = ['orange', 'grape', 'pitaya', 'blueberry']
   for index, fruit in enumerate(fruits):
       print(index, ':', fruit)

   不好的代码：

   fruits = ['orange', 'grape', 'pitaya', 'blueberry']
   index = 0
   for fruit in fruits:
       print(index, ':', fruit)
       index += 1

8. 用生成式生成列表。

   好的代码：

   data = [7, 20, 3, 15, 11]
   result = [num * 3 for num in data if num > 10]
   print(result)  # [60, 45, 33]

   不好的代码：

   data = [7, 20, 3, 15, 11]
   result = []
   for i in data:
       if i > 10:
           result.append(i * 3)
   print(result)  # [60, 45, 33]

9. 用zip组合键和值来创建字典。

   好的代码：

   keys = ['1001', '1002', '1003']
   values = ['骆昊', '王大锤', '白元芳']
   d = dict(zip(keys, values))
   print(d)

   不好的代码：

   keys = ['1001', '1002', '1003']
   values = ['骆昊', '王大锤', '白元芳']
   d = {}
   for i, key in enumerate(keys):
       d[key] = values[i]
   print(d)

说明：这篇文章的内容来自于网络，有兴趣的读者可以阅读原文：http://safehammad.com/downloads/python-idioms-2014-01-16.pdf
"""
